package com.cloudkinto.service.ob.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cloudkinto.common.BeanConvert;
import com.cloudkinto.common.constant.SysConstant;
import com.cloudkinto.common.exception.BizException;
import com.cloudkinto.common.utils.dict.StaticDict;
import com.cloudkinto.common.web.PageResult;
import com.cloudkinto.common.web.SingleResult;
import com.cloudkinto.dao.*;
import com.cloudkinto.entity.*;
import com.cloudkinto.enumType.operate.OperateTypeEnum;
import com.cloudkinto.enumType.order.inbound.ArrivalModeEnum;
import com.cloudkinto.service.ob.OperateBillService;
import com.cloudkinto.service.operate.OperateRecordService;
import com.cloudkinto.service.user.UserService;
import com.cloudkinto.utils.BeanCopierUtils;
import com.cloudkinto.vo.ft.FreightStatusUpdateReq;
import com.cloudkinto.vo.ob.*;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 操作费 service实现类
 * </p>
 *
 * @author hua
 * @since 2024-11-13
 */
@Service
@Transactional(rollbackFor = Exception.class)//事务回滚
public class OperateBillServiceImpl extends ServiceImpl<OperateBillDao, OperateBillDo> implements OperateBillService {
    @Autowired
    private OperateBillDao dao;
    @Autowired
    private OperateBillRuleDao operateBillRuleDao;
    @Autowired
    private OperateBillChannelDao operateBillChannelDao;
    @Autowired
    private UserService userService;
    @Autowired
    private LogisticsChannelDao logisticsChannelDao;
    @Autowired
    private OperateBillRuleConditionDao operateBillRuleConditionDao;
    @Autowired
    private OperateRecordService operateRecordService;
    @Autowired
    private QuotationSchemeDao quotationSchemeDao;


    @Override
    public SingleResult addInit() {
        return SingleResult.success("");
    }

    public void checkName(OperateBillEt addReq, Long id, String type, Long tenantId) {
        if (StringUtils.isBlank(addReq.getBillItemName()))
            throw new BizException(SysConstant.Add_Name_Empty);
        //名称不能重复
        OperateBillDo et = dao.selectOne(new LambdaQueryWrapper<OperateBillDo>()
                .ne(id != null, OperateBillDo::getId, id)
                .eq(OperateBillDo::getBillItemName, addReq.getBillItemName())
                .eq(OperateBillDo::getType, type)
                .eq(OperateBillDo::getTenantId, tenantId));
        if (et != null) throw new BizException(SysConstant.Add_Name_Same);
    }


    @Override
    public Object add(OperateBillAddReq req, Long userId, Long tenantId) {
        this.checkName(req, null, req.getType(), tenantId);
        OperateBillDo entityDo = new OperateBillDo();
        BeanUtils.copyProperties(req, entityDo);
        dao.insert(entityDo);
        for (OperateBillRuleAddReq rule : req.getRules()) {
            OperateBillRuleDo ruleDo = new OperateBillRuleDo();
            BeanUtils.copyProperties(rule, ruleDo);
            ruleDo.setOperateBillId(entityDo.getId());
            operateBillRuleDao.insert(ruleDo);
            //插入条件变量
            this.insertCondition(rule.getConditions(), entityDo.getId(), ruleDo.getId());
        }
        //渠道
        this.insertChannel(req.getChannels(), entityDo.getId());
        operateRecordService.operateAddRecordAndValue("更新操作费", "", entityDo.getId(), userId, null, OperateTypeEnum.OpBill);
        return entityDo.getId();
    }

    public void insertCondition(List<ObRuleConditionAddReq> conditions, Long operateBillId, Long ruleId) {
        for (ObRuleConditionAddReq condition : conditions) {
            OperateBillRuleConditionDo ruleConditionDo = new OperateBillRuleConditionDo();
            BeanUtils.copyProperties(condition, ruleConditionDo);
            ruleConditionDo.setOperateBillId(operateBillId);
            ruleConditionDo.setRuleId(ruleId);
            operateBillRuleConditionDao.insert(ruleConditionDo);
        }
    }

    public void insertChannel(List<Long> channelIds, Long billId) {
        if (channelIds == null) return;
        for (Long channelId : channelIds) {
            OperateBillChannelDo billChannelDo = new OperateBillChannelDo();
            billChannelDo.setOperateBillId(billId);
            billChannelDo.setChannelId(channelId);
            operateBillChannelDao.insert(billChannelDo);
        }
    }

    @Override
    public SingleResult updateInit(Long id) {
        return SingleResult.success("");
    }

    @Override
    public Object update(OperateBillUpdateReq req, Long userId, Long tenantId) {
        OperateBillDo entityDo = dao.selectById(req.getId());
        this.checkName(req, entityDo.getId(), entityDo.getType(), tenantId);
        //判断仓租费有没有被使用过
        int qsItemTypeUseCount = quotationSchemeDao.getQsItemTypeUseCount(req.getId(), 1, tenantId);
        if (qsItemTypeUseCount > 0) {
            if (!Objects.equals(req.getCurrencyCode(), entityDo.getCurrencyCode()))
                throw new BizException(SysConstant.OperateBill_CurrencyCode_CannotEdit);
            req.setBillItemName(entityDo.getBillItemName());
        }
        BeanUtils.copyProperties(req, entityDo);
        dao.updateById(entityDo);

        //获取旧的
        List<OperateBillRuleDo> ruleDoList = operateBillRuleDao.selectList(new LambdaQueryWrapper<OperateBillRuleDo>().eq(OperateBillRuleDo::getOperateBillId, entityDo.getId()));
        List<Long> newRuleIds = req.getRules().stream().map(OperateBillRuleUpdateReq::getId).filter(Objects::nonNull).collect(Collectors.toList());
        List<Long> deleteIds = ruleDoList.stream().map(OperateBillRuleDo::getId).filter(id -> !newRuleIds.contains(id)).collect(Collectors.toList());
        if (!deleteIds.isEmpty()) operateBillRuleDao.updateDelete(deleteIds, userId);
        List<Long> deleteConditions = req.getRules().stream().filter(i -> i.getId() != null && i.isEdit()).map(OperateBillRuleUpdateReq::getId).collect(Collectors.toList());
        deleteConditions.addAll(deleteIds);
        if (!deleteConditions.isEmpty()) operateBillRuleConditionDao.updateDelete(deleteConditions, userId);
        for (OperateBillRuleUpdateReq ruleReq : req.getRules()) {
            if (ruleReq.getId() != null && !ruleReq.isEdit()) continue;
            OperateBillRuleDo ruleDo = new OperateBillRuleDo();
            BeanUtils.copyProperties(ruleReq, ruleDo);
            if (ruleReq.getId() == null) {
                ruleDo.setOperateBillId(entityDo.getId());
                operateBillRuleDao.insert(ruleDo);
            } else {
                //修改 解决 set null 问题
                LambdaUpdateWrapper<OperateBillRuleDo> updateWrapper = new LambdaUpdateWrapper<>();
                updateWrapper.eq(OperateBillRuleDo::getId, ruleReq.getId())
                        .set(OperateBillRuleDo::getUpdateTime, new Date())
                        .set(OperateBillRuleDo::getUpdateBy, userId)
                        .set(OperateBillRuleDo::getBillCode, ruleReq.getBillCode())
                        .set(OperateBillRuleDo::getRelieQty, ruleReq.getRelieQty())
                        .set(OperateBillRuleDo::getUnitPrice, ruleReq.getUnitPrice())
                        .set(OperateBillRuleDo::getUnitCarry, ruleReq.getUnitCarry())
                        .set(OperateBillRuleDo::getUnitQty, ruleReq.getUnitQty())
                        .set(OperateBillRuleDo::getBaseFee, ruleReq.getBaseFee())
                        .set(OperateBillRuleDo::getLowestFee, ruleReq.getLowestFee())
                        .set(OperateBillRuleDo::getHighestFee, ruleReq.getHighestFee());
                operateBillRuleDao.update(null, updateWrapper);
//                operateBillRuleDao.updateById(ruleDo);
            }
            this.insertCondition(ruleReq.getConditions(), entityDo.getId(), ruleDo.getId());
        }
        //先删除 再插入
        operateBillChannelDao.updateDeleteByParent(entityDo.getId(), userId);
        this.insertChannel(req.getChannels(), entityDo.getId());
        operateRecordService.operateAddRecordAndValue("更新操作费", "", entityDo.getId(), userId, null, OperateTypeEnum.OpBill);
        return entityDo.getId();
    }

    @Override
    public int delete(Long id, Long userId, Long tenantId) {
        OperateBillDo entityDo = dao.selectById(id);
        if (entityDo == null) return 0;
        if (!Objects.equals(entityDo.getTenantId(), tenantId)) return 0;
        int qsItemTypeUseCount = quotationSchemeDao.getQsItemTypeUseCount(id, 1, tenantId);
        if (qsItemTypeUseCount > 0) throw new BizException(SysConstant.OperateBill_Related_CannotEdit);

        int i = dao.deleteById(id);
        //删除子表
        operateBillChannelDao.updateDeleteByParent(id, userId);
        operateBillRuleDao.updateDeleteByParent(id, userId);
        operateBillRuleConditionDao.updateDeleteByParent(id, userId);
        return i;
    }


    @Override
    public OperateBillDetailRes detail(Long id) {
        OperateBillDo entityDo = dao.selectById(id);
        OperateBillDetailRes res = BeanConvert.INSTANCE.operateBillDetailRes(entityDo);
        res.setUpdateByName(userService.getUserAccount(entityDo.getUpdateBy()));
        res.setCreateByName(userService.getUserAccount(entityDo.getCreateBy()));
        //渠道
        Map<Long, List<ChannelEt>> channel = this.getChannel(Arrays.asList(id));
        res.setChannel(channel.get(id));
        //规则
        List<OperateBillRuleDo> ruleDos = operateBillRuleDao.selectList(new LambdaQueryWrapper<OperateBillRuleDo>()
                .eq(OperateBillRuleDo::getOperateBillId, id));
        Map<Long, List<OperateBillRuleConditionDo>> conditonMap = operateBillRuleConditionDao.selectList(new LambdaQueryWrapper<OperateBillRuleConditionDo>()
                .eq(OperateBillRuleConditionDo::getOperateBillId, id)).stream().collect(Collectors.groupingBy(OperateBillRuleConditionDo::getRuleId));

        List<OperateBillRuleDetailRes> ruleResList = this.getRuleDetailResLit(ruleDos, conditonMap);
        res.setRules(ruleResList);
        List<String> ruleExplainList = new ArrayList<>();
        for (OperateBillRuleDetailRes detailRes : ruleResList) {
            StringBuilder builder = this.getRuleExplain(detailRes, entityDo.getCurrencyCode());
            ruleExplainList.add(builder.toString());
        }
        res.setRuleExplainList(ruleExplainList);

        res.setOperateResList(operateRecordService.getOperateList(id, OperateTypeEnum.OpBill));

        return res;
    }


    @Override
    public Map<Long, String> getRuleExplainMap(List<OperateBillRuleDo> ruleDos, Map<Long, List<OperateBillRuleConditionDo>> conditonMap, String currencyCode) {
        Map<Long, String> map = new HashMap<>();
        List<OperateBillRuleDetailRes> ruleDetailResLit = this.getRuleDetailResLit(ruleDos, conditonMap);
        for (OperateBillRuleDetailRes detailRes : ruleDetailResLit) {
            StringBuilder ruleExplain = this.getRuleExplain(detailRes, currencyCode);
            map.put(detailRes.getId(), ruleExplain.toString());
        }
        return map;
    }


    /**
     * 将do 封装一下
     */
    public List<OperateBillRuleDetailRes> getRuleDetailResLit(List<OperateBillRuleDo> ruleDos, Map<Long, List<OperateBillRuleConditionDo>> conditonMap) {
        List<OperateBillRuleDetailRes> ruleResList = BeanCopierUtils.listCopyMapper(ruleDos, OperateBillRuleDo.class, OperateBillRuleDetailRes.class);
        for (OperateBillRuleDetailRes ruleRes : ruleResList) {
            //条件变量
            if (conditonMap.get(ruleRes.getId()) == null) continue;
            ruleRes.setConditions(BeanConvert.INSTANCE.obRuleConditionAddReq(conditonMap.get(ruleRes.getId())));
        }
        return ruleResList;
    }

    /**
     * 获取计费详情
     */
    public StringBuilder getRuleExplain(OperateBillRuleDetailRes detailRes, String currencyCode) {
        StringBuilder builder = new StringBuilder();
        if (detailRes.getConditions() != null && !detailRes.getConditions().isEmpty()) {
            for (ObRuleConditionAddReq condition : detailRes.getConditions()) {
                if (builder.length() > 0) builder.append(", 并且");
                if (condition.getSetWay() == 0) {//介于 左开右臂
                    builder.append(condition.getStartVariable()).append(StaticDict.OperateBill_Sign.Less_Than.getSign())
                            .append(StaticDict.OperateBill_ConditionCode.getText(condition.getConditionCode())).append(StaticDict.OperateBill_Sign.Less_Equal.getSign())
                            .append(condition.getEndVariable());
                } else {
                    builder.append(StaticDict.OperateBill_ConditionCode.getText(condition.getConditionCode())).append(StaticDict.OperateBill_Sign.getSign(Integer.parseInt(condition.getStartVariable()))).append(condition.getEndVariable());
                }
            }
            builder.insert(0, "如果 ");
        }
        builder.append(", 费用(").append(currencyCode).append(")=");
        if (detailRes.getBaseFee() != null && detailRes.getBaseFee() > 0)
            builder.append(" ￥").append(detailRes.getBaseFee()).append("+");
        if (StaticDict.OperateBill_CalCode.getCalType(detailRes.getBillCode()) == 0) builder.append("∑("); //0求和 1单
        builder.append("￥").append(detailRes.getUnitPrice()).append("*");
        builder.append("(");
        if (detailRes.getUnitQty() > 1) builder.append("(");
        builder.append(StaticDict.OperateBill_CalCode.getText(detailRes.getBillCode()));
        if (detailRes.getRelieQty() != null && detailRes.getRelieQty() > 0)
            builder.append("-").append(detailRes.getRelieQty());
        if (detailRes.getUnitQty() > 1) builder.append(")").append("/").append(detailRes.getUnitQty());
        builder.append(")");
        if (StaticDict.OperateBill_CalCode.getCalType(detailRes.getBillCode()) == 0) builder.append(")");
        if (detailRes.getUnitCarry() == 1) builder.append(" 按计量单位进位");
        if (detailRes.getLowestFee() != null && detailRes.getLowestFee() > 0)
            builder.append(", 最低收费￥").append(detailRes.getLowestFee());
        if (detailRes.getHighestFee() != null && detailRes.getHighestFee() > 0)
            builder.append(", 最高收费￥").append(detailRes.getHighestFee());
        return builder;
    }

    public Map<Long, List<ChannelEt>> getChannel(List<Long> ids) {
        Map<Long, List<ChannelEt>> result = new HashMap<>();
        if (ids.isEmpty()) return result;
        List<OperateBillChannelDo> channelDoList = operateBillChannelDao.selectList(new LambdaQueryWrapper<OperateBillChannelDo>()
                .in(OperateBillChannelDo::getOperateBillId, ids));

        List<Long> channelId = channelDoList.stream().map(OperateBillChannelDo::getChannelId).distinct().collect(Collectors.toList());
        if (channelId.isEmpty()) return result;
        List<LogisticsChannelDo> logisticsChannelDos = logisticsChannelDao.selectList(new LambdaQueryWrapper<LogisticsChannelDo>().in(LogisticsChannelDo::getId, channelId));
        Map<Long, String> nameC = logisticsChannelDos.stream().collect(Collectors.toMap(LogisticsChannelDo::getId, LogisticsChannelDo::getChannelName));
        Map<Long, String> codeC = logisticsChannelDos.stream().collect(Collectors.toMap(LogisticsChannelDo::getId, LogisticsChannelDo::getChannelCode));

        Map<Long, List<OperateBillChannelDo>> collect = channelDoList.stream().collect(Collectors.groupingBy(OperateBillChannelDo::getOperateBillId));
        for (Long billId : collect.keySet()) {
            List<ChannelEt> c = new ArrayList<>();
            for (OperateBillChannelDo billChannelDo : collect.get(billId)) {
                c.add(new ChannelEt(billChannelDo.getChannelId(), nameC.get(billChannelDo.getChannelId()), codeC.get(billChannelDo.getChannelId())));
            }
            result.put(billId, c);
        }
        return result;
    }

    @Override
    public SingleResult pageInit() {
        Map<String, Object> result = new HashMap<>();
        result.put("billType", StaticDict.getOperateBill_Type());
        result.put("conditionCode", StaticDict.getOperateBill_ConditionCode());
        result.put("billSign", StaticDict.getOperateBill_Sign());
        result.put("billCode", StaticDict.getOperateBill_CalCode());
        result.put("arrivalMode", ArrivalModeEnum.getArrivalModeListSim());
        return SingleResult.success(result);
    }

    @Override
    public List<OperateBillPageVo> getList(Map<String, Object> map) {
        return dao.list(map);
    }

    @Override
    public PageResult getListPage(Map<String, Object> map) {
        IPage<OperateBillPageVo> page = pageInitDto(map);
        page = dao.list(page, map);
        List<OperateBillPageRes> list = BeanCopierUtils.listCopyMapper(page.getRecords(), OperateBillPageVo.class, OperateBillPageRes.class);
        //渠道
        Map<Long, List<ChannelEt>> channel = this.getChannel(list.stream().map(OperateBillPageRes::getId).collect(Collectors.toList()));
        for (OperateBillPageRes res : list) {
            res.setChannel(channel.get(res.getId()));
        }
        PageResult result = new PageResult(list, (int) page.getCurrent(), (int) page.getSize(), (int) page.getTotal(), (int) page.getPages());
        return result;
    }


    private IPage<OperateBillPageVo> pageInitDto(Map<String, Object> map) {
        IPage<OperateBillPageVo> page = new Page();
        page.setSize(map.get("pageSize") != null ? Long.parseLong(map.get("pageSize").toString()) : 10);
        page.setCurrent(map.get("current") != null ? Long.parseLong(map.get("current").toString()) : 1);
        return page;
    }

    @Override
    public void updateStatus(FreightStatusUpdateReq req, Long userId, Long tenantId) {
        OperateBillDo et = dao.selectById(req.getId());
        if (et == null) return;
        if (et.getStatus() == req.getStatus()) return;
        int qsItemTypeUseCount = quotationSchemeDao.getQsItemTypeUseCount(et.getId(), 1, tenantId);
        if (qsItemTypeUseCount > 0) throw new BizException(SysConstant.OperateBill_Related_CannotEdit);
        et.setStatus(req.getStatus());
        dao.updateById(et);
        operateRecordService.operateAddRecordAndValue(req.getStatus() == 0 ? "启动操作费" : "停用操作费", "", et.getId(), userId, null, OperateTypeEnum.OpBill);
    }

}
